#!/usr/bin/python

""" Web-interface to display RRDs produced by collectd
Please see http://code.google.com/p/stats-display/ for documentation
"""

import sys, rrdtool, time, os, urllib, yaml, socket, logging, re

LOG_FILENAME = '/tmp/stats_display.log'
logger = logging.getLogger('MyLogger')
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")

fh = logging.FileHandler(LOG_FILENAME)
fh.setFormatter(formatter)
logger.addHandler(fh)


class NoDataException(Exception):
    pass


class CollectdGraph(object):
    def __init__(self, config, host, plugin, device=None, metric=None):
        self.config = config
        self.host = host
        self.plugin = plugin
        self.device = device
        self.metric = metric
        self.rra_vars = {
            'rrdpath': self.config['rrdpath'],
            'plugin_path': os.path.join(self.config['rrdpath'], self.host),
            'host':   self.host,
            'plugin': self.plugin,
            'device': self.device,
            'metric': self.metric,
        }

        self.rrd_file = self.config['metrics'][self.plugin]['rrd_file'] % self.rra_vars
        self.rra_vars['rrd_file'] = self.rrd_file

        if self.config['metrics'][self.plugin].has_key('title'):
            self.title = self.config['metrics'][self.plugin]['title'] % self.rra_vars
        else:
            self.title = '%(plugin)s %(device)s %(metric)s' % self.rra_vars

    def render(self, timespan):
        endtime = int(time.time())

        rra = self.config['metrics'][self.plugin]['rra']
        rra = [r % self.rra_vars for r in rra]

        try:
            base = self.config['metrics'][self.plugin]['base']
        except KeyError:
            base = 1000

        vg = rrdtool.graphv(
            '-',
            '--start',      str(int(endtime) - int(timespan)),
            '--end',        str(endtime),
            '--imgformat',  'PNG' ,
            '--title',      self.title,
            '--lower-limit', '0',
            '--base',       str(base),
            '--width',      str(self.config['width']),
            '--height',     str(self.config['height']),
            '--slope-mode',
            *rra
        )

        return vg['image']


class WSGIApp(object):
    _config = None
    def __init__(self, environ, start_response, config_f='wcollectd.yml'):
        self.environ = environ
        self.start_response = start_response
        self.GET = self.parse_getargs()
        if not self.__class__._config:
            y_f = open(config_f)
            self.__class__._config = yaml.load(y_f)
            y_f.close()
        self.config = self.__class__._config

    def parse_getargs(self):
        pairs = self.environ['QUERY_STRING'].split('&')
        GET = {'timespan': '86400'}
        for pair in pairs:
            try:
                (k, v) = pair.split('=')
                GET[k] = v
            except ValueError:
                pass
        return GET

    def list_metrics(self, host):
        display_order = self.config['display_order']
        rrd_files = []

        def sorter(x, y):
            try:
                px = display_order.index(x)
            except:
                px = 1
            try:
                py = display_order.index(y)
            except:
                py = 1
            return cmp(px, py)

        def callback(arg, dirname, fnames):
            for f in fnames:
                fullpath = os.path.join(dirname, f)
                if os.path.isfile(fullpath) and os.path.splitext(fullpath)[1] == '.rrd':
                    rrd_files.append(fullpath)

        base_path = os.path.join(self.config['rrdpath'], host)
        os.path.walk(base_path, callback, None)
        rrd_files.sort()
        metrics_to_display = []
        plugins = self.config['metrics'].keys()
        plugins.sort(cmp=sorter)

        for p in plugins:
            m_re = self.config['metrics'][p]['rrd_file'] % {
                'plugin_path': base_path,
                'device': r'(?P<device>[^/]+)',
                'metric': r'(?P<metric>[^/]+)',
            }
            m_re = m_re.replace('.', r'\.') + '$'
            for f in rrd_files:
                m_obj = re.search(m_re, f)
                if m_obj:
                    graph = m_obj.groupdict()
                    graph['host'] = host
                    graph['plugin'] = p
                    metrics_to_display.append(graph)
                    if not (graph.has_key('metric') or graph.has_key('device')):
                        break
        return metrics_to_display

    @property
    def hosts(self):
        hosts = [f for f in os.listdir(self.config['rrdpath']) if os.path.isdir(os.path.join(self.config['rrdpath'], f))]
        return hosts

    def render_page(self):
        page = """<html><head><title>%(host)s metrics</title></head>
<body><table><tr><td width="300px"></td>
<td valign="top" style="padding-left: 10px"><a href="?timespan=3600&host=%(host)s">hour</a>
 | <a href="?timespan=86400&host=%(host)s">day</a>
 | <a href="?timespan=604800&host=%(host)s">week</a>
 | <a href="?timespan=2592000&host=%(host)s">month</a>
 | <a href="?timespan=31536000&host=%(host)s">year</a>
</td></tr><tr><td valign="top" align="right" style="padding-right: 10px">%(hostsblock)s</td>
<td>%(imageblock)s</td></tr></table></body></html>"""
        imageblocks = []
        hostname = self.GET.get('host', None) or socket.gethostname()
        for graph in self.list_metrics(hostname):
            uri = '<p><img src="?host=%s&plugin=%s&timespan=%s' % (graph['host'], graph['plugin'], self.GET['timespan'])
            if graph.has_key('device'):
                uri = uri + '&device=%s' % graph['device']
            if graph.has_key('metric'):
                uri = uri + '&metric=%s' % graph['metric']
            uri = uri + '"/></p>'
            imageblocks.append(uri)

        hostsblocks = []
        for h in self.hosts:
            hostsblocks.append('<p %s><a href="?timespan=%s&host=%s">%s</a></p>' % (
                (h == hostname) and 'style="font-weight: bold"' or '',
                self.GET['timespan'], h, h))

        page = page % {'host': hostname,
                       'imageblock': "\n".join(imageblocks),
                       'hostsblock': "\n".join(hostsblocks)}
        return page

    def render(self):
        if self.GET.has_key('plugin'):
            try:
                collectGraph = CollectdGraph(self.config,
                                            self.GET['host'],
                                            self.GET['plugin'],
                                            self.GET.get('device', None),
                                            self.GET.get('metric', None),
                                        )
                self.start_response("200 OK", [('content-type', 'image/png'), ('cache-control', 'no-cache')])
                return collectGraph.render(timespan=self.GET['timespan'])
            except NoDataException:
                self.start_response("404 NotFound", [('content-type', 'text/html'), ('cache-control', 'no-cache')])
                return ['<h1>Metric Not Found</h1>']
            except:
                logger.exception('Internal Error')
                self.start_response("500 Application Error", [('content-type', 'text/html'), ('cache-control', 'no-cache')])
                return ['<h1>Application error, please check log</h1>']
        else:
            self.start_response("200 OK", [('content-type', 'text/html'), ('cache-control', 'no-cache')])
            return self.render_page()


def application(environ, start_response):
    try:
        config_f = environ.get('WCOLLECTD_CONFIG')
    except KeyError:
        raise Exception, 'Env variable WCOLLECTD_CONFIG is not set'
    app = WSGIApp(environ, start_response, config_f=config_f)
    return app.render()


if __name__ == "__main__":
    print "This is WSGI script. Please see \
http://code.google.com/p/stats-display/ for setup instructions"
    sys.exit(1)

